package com.tencent.sr.iris.activity.service.event.task;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.tencent.sr.iris.activity.common.enums.ActivityPeriodTypeEnum;
import com.tencent.sr.iris.activity.common.enums.EventHandingRecordStatusEnum;
import com.tencent.sr.iris.activity.common.enums.UserTaskStatusEnum;
import com.tencent.sr.iris.activity.domain.event.dto.ActivityDTO;
import com.tencent.sr.iris.activity.domain.event.dto.ActivityTaskDTO;
import com.tencent.sr.iris.activity.interfaces.enums.ActivityTypeEnum;
import com.tencent.sr.iris.activity.interfaces.event.dto.ActivityEventDTO;
import com.tencent.sr.iris.activity.interfaces.request.acticity.QueryUserTaskRecordFromDBRequest;
import com.tencent.sr.iris.activity.repository.entity.TIrisEventHandingRecordDO;
import com.tencent.sr.iris.activity.repository.entity.TIrisUserTaskRecordDO;
import com.tencent.sr.iris.activity.service.activity.IrisEventHandingRecordService;
import com.tencent.sr.iris.activity.service.activity.IrisUserTaskRecordService;
import com.tencent.sr.iris.activity.service.event.vo.BaseUserTaskDataVO;
import com.tencent.sr.iris.activity.service.event.vo.TaskExecutorExtendInfo;
import com.tencent.sr.iris.activity.service.util.IdCreateorUtil;
import com.tencent.sr.rmall.common.exception.retail.TRetailBizException;
import com.tencent.sr.rmall.common.exception.retail.TRetailErrorException;
import com.tencent.sr.rmall.springbootstarter.monitor.util.MonitorUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import javax.annotation.Resource;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 任务处理模版
 *
 * @author xiaojun
 */
@Slf4j
public abstract class AbstractIrisTaskExecutor implements IrisTaskExecutor {

    @Resource
    IrisEventHandingRecordService irisEventHandingRecordService;
    @Resource
    private IrisUserTaskRecordService irisUserTaskRecordService;
    @Resource
    private IdCreateorUtil idCreateorUtil;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private IrisUserTaskRecordService userTaskRecordService;

    /**
     * 任务处理器
     *
     * @param eventDTO
     * @param act
     * @param task
     * @param taskExecutorExtendInfo
     */
    @Override
    public void execute(ActivityEventDTO eventDTO, ActivityDTO act, ActivityTaskDTO task,
                        TaskExecutorExtendInfo taskExecutorExtendInfo) {
        Throwable throwable = null;
        long startTime = System.currentTimeMillis();
        List<String> eventHandingInfoList = Lists.newArrayList();
        TIrisEventHandingRecordDO handingRecordDO = null;
        RLock lock = null;
        try {
            //在事件处理handle中已经加了事件幂等id的分布式锁，到这里的任务维度的事件处理流水，不会出现并发，不需要加锁
            handingRecordDO = irisEventHandingRecordService.queryRecord(eventDTO, act, task);
            if (handingRecordDO == null) {
                handingRecordDO = irisEventHandingRecordService.saveHandingRecord(eventDTO, act, task,
                        eventHandingInfoList);
            }
            //一个用户在一个任务周期内只能参加一次，这里的分布式锁是防止同一个用户的同一个周期的任务事件同时进来，
            // 由于目前事件只是触发统计，所以此时只统计一次就行了
            String lockKey = getLockKey(eventDTO, act, task);
            lock = redissonClient.getLock(lockKey);
            if (!lock.tryLock(10, TimeUnit.SECONDS)) {
                eventHandingInfoList.add("任务处理：获取分布式锁失败，当前任务事件正在处理，同意任务时间段的多次触发");
                log.info("任务处理：获取分布式锁失败，当前任务事件正在处理，同意任务时间段的多次触发，事件idempotent:{}", eventDTO.getIdempotent());
                return;
            }

            //前置处理
            before(eventDTO, act, task);
            //处理事件
            process(eventDTO, act, task, eventHandingInfoList, taskExecutorExtendInfo);
            //后置处理
            after(eventDTO, act, task);

            handingRecordDO.setHandingInfo(JSON.toJSONString(eventHandingInfoList));
            handingRecordDO.setHandingStatus(EventHandingRecordStatusEnum.SUCCEES.getCode());
        } catch (TRetailBizException e) {
            //业务异常只做中断，不重试
            eventHandingInfoList.add(e.getMessage());
            handingRecordDO.setHandingInfo(JSON.toJSONString(eventHandingInfoList));
            handingRecordDO.setHandingStatus(EventHandingRecordStatusEnum.SUCCEES.getCode());
            log.info("任务处理业务异常 eventIdempotent:{},actBizId:{},taskId:{},error:{}", eventDTO.getIdempotent(),
                    act.getBizId(), task.getBizId(), e.getMessage());
        } catch (TRetailErrorException e) {
            //业务错误中断，需要重试
            eventHandingInfoList.add(e.getMessage());
            handingRecordDO.setHandingInfo(JSON.toJSONString(eventHandingInfoList));
            handingRecordDO.setFailCount((byte) (handingRecordDO.getFailCount() + 1));
            handingRecordDO.setHandingStatus(EventHandingRecordStatusEnum.FAIL.getCode());
            throwable = e;
            log.error("任务处理业务错误 eventIdempotent:{},actBizId:{},taskId:{}", eventDTO.getIdempotent(),
                    act.getBizId(), task.getBizId(), e);
        } catch (Exception e) {
            //系统异常，需要重试
            eventHandingInfoList.add("系统异常");
            handingRecordDO.setHandingInfo(JSON.toJSONString(eventHandingInfoList));
            handingRecordDO.setFailCount((byte) (handingRecordDO.getFailCount() + 1));
            handingRecordDO.setHandingStatus(EventHandingRecordStatusEnum.FAIL.getCode());
            throwable = e;
            log.error("任务处理系统异常 eventIdempotent:{},actBizId:{},taskId:{}", eventDTO.getIdempotent(),
                    act.getBizId(), task.getBizId(), e);
        } finally {
            irisEventHandingRecordService.updateRecord(handingRecordDO);
            if (lock != null && lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
            log.info("任务处理：taskId:{},耗时：{}", task.getBizId(), (System.currentTimeMillis() - startTime));
            MonitorUtil.getSingleMonitorService().logTransaction("activity_event_handler", "task",
                    startTime, throwable);
        }

    }

    public void process(ActivityEventDTO eventDTO, ActivityDTO act, ActivityTaskDTO task,
                        List<String> eventHandingInfoList, TaskExecutorExtendInfo taskExecutorExtendInfo) {
        //用户数据准备
        BaseUserTaskDataVO userTaskDataVO = prepareUserTaskData(eventDTO, act, task);
        //任务门槛校验
        boolean success = checkTaskThreshold(eventDTO, act, task, userTaskDataVO);
        if (success) {
            boolean isok = executeAward(eventDTO, act, task);
            if (isok) {
                eventHandingInfoList.add("发奖成功");
            } else {
                eventHandingInfoList.add("发奖失败");
            }
        } else {
            eventHandingInfoList.add("未达到任务门槛,userTaskDataVO:" + JSON.toJSONString(userTaskDataVO));
            //未达到任务门槛，记录日志，返回
            log.info("未达到任务门槛");
        }

    }

    /**
     * 获取任务的分布式锁
     *
     * @param eventDTO
     * @param act
     * @param task
     * @return
     */
    private String getLockKey(ActivityEventDTO eventDTO, ActivityDTO act, ActivityTaskDTO task) {
        String period = getEventActPeriod(eventDTO, act);
        return getLockKey(eventDTO.getUid(), act.getBizId(), task.getBizId(), period);
    }

    /**
     * 获取任务的分布式锁
     * @param uid
     * @param activityId
     * @param taskId
     * @param period
     * @return
     */
    public String getLockKey(String uid, String activityId, String taskId, String period) {
        return String.format("irisTaskExecutor_lock_%s_%s_%s_%s", uid, activityId, taskId, period);
    }

    /**
     * 获取事件在当前活动的周期
     *
     * @param eventDTO
     * @param act
     * @return
     */
    private String getEventActPeriod(ActivityEventDTO eventDTO, ActivityDTO act) {
        String period = StrUtil.EMPTY;
        //是周期活动才有周期，这里没有区分周期是年/月/日
        if (ActivityPeriodTypeEnum.COMMON.getCode() != act.getPeriodType()) {
            LocalDateTime localDateTime = LocalDateTime
                    .ofInstant(Instant.ofEpochMilli(eventDTO.getEventTime()), ZoneId.systemDefault());
            period = localDateTime.format(DateTimeFormatter.ofPattern(act.getPeriod()));
        }
        return period;
    }

    void before(ActivityEventDTO eventDTO, ActivityDTO act, ActivityTaskDTO task) {
        Date eventDateTime = new Date(eventDTO.getEventTime());
        if (eventDateTime.before(task.getStartTime()) || eventDateTime.after(task.getEndTime())
                || task.getEndTime().before(task.getStartTime())) {
            log.info("任务处理：事件时间不在任务时间范围内，不处理，eventIdempotent:{},"
                            + "eventTime:{},taskId:{},taskTime:{}-{}", eventDTO.getIdempotent(), eventDateTime,
                    task.getBizId(), task.getStartTime(), task.getEndTime());
            throw new TRetailBizException("事件时间不在任务时间范围内，不处理");
        }
        taskCheck(eventDTO, act, task);
    }

    public void after(ActivityEventDTO eventDTO, ActivityDTO act, ActivityTaskDTO task) {

    }

    /**
     * 填充用户参与任务记录的状态
     *
     * @param eventDTO
     * @param act
     * @param task
     * @param userTaskRecordDO
     * @param currentLocalDate
     */
    public abstract void fillTaskRecordStatus(ActivityEventDTO eventDTO, ActivityDTO act, ActivityTaskDTO task,
                                              TIrisUserTaskRecordDO userTaskRecordDO, LocalDate currentLocalDate);

    /**
     * 执行发奖
     *
     * @param event
     * @param act
     * @param task
     * @return
     */
    public abstract boolean executeAward(ActivityEventDTO event, ActivityDTO act, ActivityTaskDTO task);

    /**
     * 校验门槛
     *
     * @param event
     * @param act
     * @param task
     * @param userTaskDataVO
     * @return
     */
    public abstract boolean checkTaskThreshold(ActivityEventDTO event, ActivityDTO act, ActivityTaskDTO task,
                                               BaseUserTaskDataVO userTaskDataVO);

    /**
     * 准备用户任务的数据
     *
     * @param event
     * @param act
     * @param task
     */
    public abstract BaseUserTaskDataVO prepareUserTaskData(ActivityEventDTO event, ActivityDTO act,
                                                           ActivityTaskDTO task);

    /**
     * 任务前置校验
     *
     * @param event
     * @param act
     * @param task
     */
    void taskCheck(ActivityEventDTO event, ActivityDTO act, ActivityTaskDTO task) {
        //任务基础信息校验，任务时间等

        //任务扩展校验
        taskExtCheck(event, act, task);
    }

    /**
     * 任务扩展校验
     *
     * @return
     */
    void taskExtCheck(ActivityEventDTO event, ActivityDTO act, ActivityTaskDTO task) {
        return;
    }

    /**
     * 查询用户活动任务完成进度
     *
     * @return
     */
    public TIrisUserTaskRecordDO queryUserTaskRecord(String uid, String activityId, String taskId,
                                                     String period) {
        QueryUserTaskRecordFromDBRequest request = QueryUserTaskRecordFromDBRequest
                .builder().uid(uid).activityId(activityId).taskId(taskId).period(period).build();
        return irisUserTaskRecordService.queryUserTaskRecord(request);
    }

    public TIrisUserTaskRecordDO buildUserTaskRecordDO(ActivityDTO activity, ActivityTaskDTO taskDTO,
                                                       ActivityEventDTO msg) {
        String taskPeriod = null;
        if (ActivityPeriodTypeEnum.PERIOD.getCode() == activity.getPeriodType()) {
            taskPeriod = LocalDate.now().format(DateTimeFormatter.ofPattern(activity.getPeriod()));
        }
        TIrisUserTaskRecordDO userTaskRecordDO = new TIrisUserTaskRecordDO();
        userTaskRecordDO.setBizId(idCreateorUtil.genId());
        userTaskRecordDO.setUid(msg.getUid());
        userTaskRecordDO.setPid(Optional.ofNullable(msg.getPid()).orElse(""));
        userTaskRecordDO.setActivityId(activity.getBizId());
        userTaskRecordDO.setTaskId(taskDTO.getBizId());
        userTaskRecordDO.setActivityType(activity.getActivityType());
        userTaskRecordDO.setPeriodType(activity.getPeriodType());
        userTaskRecordDO.setTaskPeriod(taskPeriod);
        userTaskRecordDO.setUserTaskStatus(UserTaskStatusEnum.FINISHED.getCode());
        userTaskRecordDO.setEstimateUserTaskStatus(UserTaskStatusEnum.FINISHED.getCode());
        Date date;
        if (Objects.equals(ActivityTypeEnum.GUIDE_ALLOWANCE.getCode(), activity.getActivityType())) {
            date = new Date(msg.getEventTime());
        } else {
            date = new Date();
        }
        userTaskRecordDO.setCurrentTaskEndTime(date);
        userTaskRecordDO.setCurrentTaskStartTime(date);
        userTaskRecordService.saveUserTaskRecord(userTaskRecordDO);
        return userTaskRecordDO;
    }

    public Date TraineeTimeTo29(Long becomeTraineeTime) {
        Calendar cal = Calendar.getInstance();
        Date traineeDate = new Date(becomeTraineeTime);
        cal.setTime(traineeDate);
        cal.set(Calendar.HOUR_OF_DAY, 23);
        cal.set(Calendar.MINUTE, 59);
        cal.set(Calendar.SECOND, 59);
        cal.add(Calendar.DATE, 29);
        return cal.getTime();
    }
}
